
#include <stdlib.h>
#include <string.h>

#include "eval_impl.h"


static Obj *
default_id_evaluator_handler( void * unused_tag, Id * id )
{
    return (Obj *)id;
}

EvalContext *
create_evaluator( AllocContext * alloc_ctx )
{
    EvalContext * eval = ql_mallocz( alloc_ctx, sizeof(EvalContext) );
    eval->alloc_ctx = alloc_ctx;
    
    set_id_evaluator_handler( eval, &default_id_evaluator_handler, 0 );
    
    return eval;
}

EvalIdHandler
set_id_evaluator_handler( EvalContext * eval, EvalIdHandler eval_id_cb, void * eid_handler_tag )
{
    EvalIdHandler       prev_eval_id_cb = eval->eval_id_cb;

    eval->eval_id_cb = eval_id_cb;
    eval->eval_id_cb_tag = eid_handler_tag;

    return prev_eval_id_cb;
}

EvalErrorCallback
set_evaluator_callback( EvalContext * e, EvalErrorCallback error_cb )
{
    EvalErrorCallback   prev_error_cb = e->error_cb;

    e->error_cb = error_cb;

    return prev_error_cb;
}

void *
set_evaluator_tag( EvalContext * eval, void * tag )
{
    void * prev_tag = eval->tag;
    
    eval->tag = tag;

    return prev_tag;
}

void *
get_evaluator_tag( EvalContext * eval )
{
    return eval->tag;
}

ErrResult
get_evaluator_last_error( EvalContext * eval )
{
    return eval->last_err;
}

AllocContext *
get_evaluator_allocator( EvalContext * eval )
{
    return eval->alloc_ctx;
}

void
free_evaluator( EvalContext * e )
{
    if( 0 != e->funcs_arr.arr )
    {
        ql_free( e->funcs_arr.arr );
    }

    ql_free( e );
}



/*****************************************************************************
 * evaluation facility
 *****************************************************************************/

/*
 * evaluates arguments in function call.
 * function name does not evaluated
 */
static List *
create_evaluated_args_list( EvalContext * eval, List * decl )
{
    List *      eval_decl = alloc_list( eval->alloc_ctx );
    List *      decl_iter;

    // add evaluated arguments
    for( decl_iter = decl->cdr; decl_iter != 0; decl_iter = decl_iter->cdr )
    {
        list_add( eval->alloc_ctx, eval_decl, internal_evaluate(eval, decl_iter->car) );
    }

    return eval_decl;
}

static Obj *
evaluate_list( EvalContext * eval, List * decl )
{
    Id *            id_proc;
    FunctionAssoc * f_assoc;
    List *          args;

    // list is empty - this is the same as if nil is provided
    if( decl->car == 0 )
    {
        return g_nil_obj;
    }

    // evaluated first argument shall always be identifier
    id_proc = (Id *)internal_evaluate(eval, decl->car);
    if( id_proc->type != OBJ_ID )
    {
        evaluator_emit_error( eval, QL_ERR_FIRST_TOKEN_ISNT_FUNCTION_NAME );
    }

    f_assoc = find_function( eval, id_proc );
    if( f_assoc == 0 )
    {
        evaluator_emit_error( eval, QL_ERR_CALL_TO_UNDEFINED_FUNCTION );
    }

    if( f_assoc->args_evaluation_needed )
    {
        List *      eval_decl = create_evaluated_args_list( eval, decl );
        list_push( eval->alloc_ctx, eval_decl, (Obj *)id_proc );

        args = eval_decl->cdr;
    }
    else
    {
        args = decl->cdr;
    }
    
    return f_assoc->handler( f_assoc->tag, args );
}

Obj *
internal_evaluate( EvalContext * eval, Obj * decl )
{
    if( decl->type != OBJ_LIST )
    {
        if( decl->type == OBJ_ID )
        {
            return eval->eval_id_cb( eval->eval_id_cb_tag, (Id *)decl );
        }

        return decl;
    }
    
    return evaluate_list( eval, (List *)decl );
}


void
evaluator_emit_error( EvalContext * eval, ErrResult err )
{
    eval->last_err = err;

    // notify subscriber about an error while evaluating list
    if( eval->error_cb != 0 )
    {
        eval->error_cb( eval );
    }

    longjmp( eval->err_trap, 1 );
}

Obj *
evaluate( EvalContext * eval, Obj * obj )
{
    Obj *   result = 0;
    jmp_buf old_err_trap;

    // save jmp buffer
    memcpy( old_err_trap, eval->err_trap, sizeof(jmp_buf) );

    if( 0 == setjmp(eval->err_trap) )
    {
        result = internal_evaluate( eval, obj );
    }

    // restore previous jmp buffer
    memcpy( eval->err_trap, old_err_trap, sizeof(jmp_buf) );

    return result;
}


/*****************************************************************************
 * functions facility
 *****************************************************************************/

static int
funcs_arr_comparison( const void * l, const void * r )
{
    const FunctionAssoc *   lhs = l;
    const FunctionAssoc *   rhs = r;

    return idcmp( lhs->function_name, rhs->function_name );
}

bool
add_function2(
    EvalContext *       eval,
    Id *                function_name,
    FunctionHandler     handler,
    bool                args_evaluation_needed,
    void *              tag
    )
{
    FuncsArr *      f = &eval->funcs_arr;
    FunctionAssoc * a = find_function( eval, function_name );

    if( 0 != a )
    {
        return false;
    }

    if( f->size + 1 > f->allocated )
    {
        f->allocated += QL_FUNCS_ARR_GROW_SIZE;
        f->arr = ql_realloc( eval->alloc_ctx, f->arr, f->allocated * sizeof(FunctionAssoc) );
    }

    a = &f->arr[f->size ++];

    // initialize this one
    a->function_name = function_name;
    a->tag = tag;
    a->handler = handler;
    a->args_evaluation_needed = args_evaluation_needed;

    // sort array so we'll be able to use bsearch then
    qsort( f->arr, f->size, sizeof(FunctionAssoc), funcs_arr_comparison );

    return true;
}

bool
add_function( EvalContext * eval, Id * function_name, FunctionHandler handler )
{
    return add_function2( eval, function_name, handler, false, eval );
}

FunctionAssoc *
find_function( EvalContext * eval, Id * function_name )
{
    FunctionAssoc   key;
    FuncsArr *      f = &eval->funcs_arr;

    key.function_name = function_name;
    return bsearch( &key, f->arr, f->size, sizeof(FunctionAssoc), funcs_arr_comparison );
}

